#!/usr/bin/python3
# -*- coding: utf-8 -*-

import json
import xml2dict


class Signal():
    def __init__(self):
        self.period = 0
        self.value_list = []

    def set_name(self, name: str):
        self.name = name

    def get_value(self, elapsed: int):
        pass  # to be overwritten

    def parse_attribute(self, attrs: dict):
        if 'name' in attrs:
            self.name = attrs['name']
        elif '@name' in attrs:
            self.name = attrs['@name']
        else:
            raise

        if 'type' in attrs:
            self.type = attrs['type']
        elif '@type' in attrs:
            self.type = attrs['@type']
        else:
            raise

    def __str__(self):
        return ('name=%s type=%s period=%d' % (
            self.name,
            self.type,
            self.period))


class SquareWave(Signal):
    def __init__(self, attrs: dict):
        super(SquareWave, self).__init__()
        self.search_table = []
        if attrs is not None:
            self.parse_attribute(attrs)

    def get_value(self, elapsed: int):
        residual = elapsed % self.period

        for item in self.search_table:
            if residual < item[0]:
                return item[1]

        return None

    def parse_one_value_attr(self, value_attr: dict, t: int):
            if 'value' in value_attr:
                value = float(value_attr['value'])
            elif '@value' in value_attr:
                value = float(value_attr['@value'])
            else:
                raise

            if 'duration' in value_attr:
                duration = int(value_attr['duration'])
            elif '@duration' in value_attr:
                duration = int(value_attr['@duration'])
            else:
                raise

            print('\tkeep %.1f for %d' % (value, duration))
            self.value_list.append((duration, value))
            t += duration
            self.search_table.append((t, value))

            return t

    def parse_attribute(self, attrs: dict):
        Signal.parse_attribute(self, attrs)
        print(self.name + ':')

        # reset value_list, period and search_table
        self.period = 0
        self.value_list = []
        self.search_table = []
        t = 0
        values = attrs['Value']

#       print(json.dumps(values, indent=4))
        if isinstance(values, list):
            for value_attr in values:
                t = self.parse_one_value_attr(value_attr, t)
        elif isinstance(values, dict): # only one value
            t = self.parse_one_value_attr(values, t)
        else:
            raise

        self.period = t


class ZigzagWave(Signal):
    def __init__(self, attrs: dict):
        super(ZigzagWave, self).__init__()
        self.search_table = []
        if attrs is not None:
            self.parse_attribute(attrs)

    def get_value(self, elapsed: int):
        residual = elapsed % self.period

        for item in self.search_table:
            t_s = item[0]
            t_e = item[1]
            a = item[2]
            b = item[3]

            if residual < t_e:
                return a * (residual - t_s) + b

        return None

    def parse_one_value_attr(self, value_attr: dict, t: int):
        if 'begin_value' in value_attr:
            begin_v = float(value_attr['begin_value'])
        elif '@begin_value' in value_attr:
            begin_v = float(value_attr['@begin_value'])
        else:
            raise

        if 'end_value' in value_attr:
            end_v = float(value_attr['end_value'])
        elif '@end_value' in value_attr:
            end_v = float(value_attr['@end_value'])
        else:
            raise

        if 'interval' in value_attr:
            interval = int(value_attr['interval'])
        elif '@interval' in value_attr:
            interval = int(value_attr['@interval'])
        else:
            raise

        print('\tfrom %.1f to %.1f in %d' % (begin_v, end_v, interval))
        self.value_list.append((interval, begin_v, end_v))
        prev_t = t
        t += interval
        a = (end_v - begin_v) / interval
        print('\tprev_t=%d t=%d a=%.3f b=%.2f' % (prev_t, t, a, begin_v))
        self.search_table.append((prev_t, t, a, begin_v))

        return t

    def parse_attribute(self, attrs: dict):
        Signal.parse_attribute(self, attrs)

        print(self.name + ':')

        # reset value_list, period and search_table
        self.period = 0
        self.value_list = []
        self.search_table = []
        t = 0
        values = attrs['Value']
#       print(json.dumps(values, indent=4))
        if isinstance(values, list):
            for value_attr in values:
                t = self.parse_one_value_attr(value_attr, t)
        elif isinstance(values, dict): # only one value
            t = self.parse_one_value_attr(values, t)
        else:
            raise

        self.period = t


class SequentialWave(Signal):
    def __init__(self, attrs: dict):
        super(SequentialWave, self).__init__()

        self.value_index = 0
        self.value_num = 0

        if attrs is not None:
            self.parse_attribute(attrs)

    # the elapsed is ignored by this function
    def get_value(self, elapsed: int = 0):
        value = None

        n = len(self.value_list)
        if self.value_index < n:
            value = self.value_list[self.value_index]

        self.value_index += 1
        if self.value_index >= n:
            self.value_index = 0

        return value

    def parse_one_value_attr(self, value_attr: dict):
        if 'value' in value_attr:
            value = float(value_attr['value'])
        elif '@value' in value_attr:
            value = float(value_attr['@value'])
        else:
            raise

        if 'repeat' in value_attr:
            repeat = int(value_attr['repeat'])
        elif '@repeat' in value_attr:
            repeat = int(value_attr['@repeat'])
        else:
            repeat = 1

        print('\tvalue=', value, '\trepeat=', repeat)
        for i in range(0, repeat):
            self.value_list.append(value)

    def parse_attribute(self, attrs: dict):
        Signal.parse_attribute(self, attrs)
        print(self.name + ':')

        # reset value_list, period and search_table
        self.value_list = []
        values = attrs['Value']
#       print(json.dumps(values, indent=4))
        if isinstance(values, list):
            for value_attr in values:
                t = self.parse_one_value_attr(value_attr)
        elif isinstance(values, dict): # only one value
            t = self.parse_one_value_attr(values)
        else:
            raise


class SinWave(Signal):
    def __init__(self, attrs: dict):
        super(SinWave, self).__init__()
        if attrs is not None:
            self.parse_attribute(attrs)

    def get_value(self, elapsed: int):
#       return self.value_list(elapsed)
        return 0

    def parse_attribute(self, attrs: dict):
        Signal.parse_attribute(self, attrs)

        print(self.name + ':')
        if 'period' in attrs:
            self.period = float(attrs['period'])
        elif '@period' in attrs:
            self.period = float(attrs['@period'])
        else:
            raise

        if 'amplitude' in attrs:
            self.amplitude = float(attrs['amplitude'])
        elif '@amplitude' in attrs:
            self.amplitude = float(attrs['@amplitude'])
        else:
            raise

        if 'shift' in attrs:
            self.shift = float(attrs['shift'])
        elif '@shift' in attrs:
            self.shift = float(attrs['@shift'])
        else:
            raise

        if 'vshift' in attrs:
            self.vshift = float(attrs['vshift'])
        elif '@vshift' in attrs:
            self.vshift = float(attrs['@vshift'])
        else:
            raise

        print('\tperiod=%.1f amplitude=%.1f shift=%.1f vshift=%.1f' % (
            self.period,
            self.amplitude,
            self.shift,
            self.vshift))


class SignalGenerator():
    def __init__(self):
        self.signal_list = []
        self.da = None

    def set_data_agent(self, da):
        self.da = da

    def parse_signal(self, signals: list):
#       print('\nParsing signals')
        for s_attr in signals:
#           print(json.dumps(s_attr, indent=4))

            if 'type' in s_attr:
                signal_type = s_attr['type']
            elif '@type' in s_attr:
                signal_type = s_attr['@type']
            else:
                print('No type is specified in Signal:', s_attr, '!')
                raise

            if signal_type == 'square':
                s_obj = SquareWave(s_attr)
                self.signal_list.append(s_obj)
            elif signal_type == 'zigzag':
                s_obj = ZigzagWave(s_attr)
                self.signal_list.append(s_obj)
            elif signal_type == 'sequence':
                s_obj = SequentialWave(s_attr)
                self.signal_list.append(s_obj)
            elif signal_type == 'sin':
                s_obj = SinWave(s_attr)
                self.signal_list.append(s_obj)
            # extend other types here!
            else:
                print('Unsupported type', signal_type, '!')
                raise

    def search(self, name: str):
        for signal in self.signal_list:
            if signal.name == name:
                return signal

        return None

    def query(self, name: str, elapsed: int):
        signal = self.search(name)

        if signal is not None:
            value = signal.get_value(elapsed)

            if isinstance(signal, SequentialWave) and self.da is not None:
                self.da.publish(name, value)

            return value

        else:
            return None

    def query_all(self, elapsed: int):
        value_dict = {}

        for signal in self.signal_list:
            value = signal.get_value(elapsed)

            if isinstance(signal, SequentialWave) and self.da is not None:
                self.da.publish(signal.name, value)

            value_dict[signal.name] = value

        return value_dict
